#ifndef _COMPILATION_GRAPH_TASK_H_
#define _COMPILATION_GRAPH_TASK_H_

#include <boost/graph/adjacency_list.hpp>

#include <compilation/task/Task.h>
#include <compilation/task/Handler.h>
#include <compilation/task/Propagator.h>

#include <compilation/Function.h>


// GraphTask and GraphAgenda.
namespace compilation
{

class GraphTask
    : public VirtualTask
{
public:

  GraphTask(VirtualInstruction& instruction);

  virtual void applyOn(
      VirtualInstruction &instruction,
      VirtualAgenda &continuations);

  virtual VirtualTask* clone(void) const;

  virtual bool mergeWith(VirtualTask &source);
};

struct GraphAgendaEdge {};

struct GraphAgendaVertex
{
  int instruction_id;
  std::string text;
  VirtualInstruction* instruction;
};

class GraphAgenda
    : public VirtualAgenda
{
public:

  friend class GraphTask;

  typedef GraphTask Task;

  typedef GraphAgendaEdge   Edge;
  typedef GraphAgendaVertex Vertex;

  typedef boost::adjacency_list<
    boost::setS,
    boost::vecS,
    boost::directedS,
    Vertex,
    Edge
  > Graph;

  static void addInstructionInGraph(
      VirtualInstruction& instruction,
      GraphAgenda &agenda);

  static bool connectInstructionsInGraph(
      VirtualInstruction& instruction1,
      VirtualInstruction& instruction2);

  typedef InstructionVisitor<
  void, GraphTask&, GraphAgenda&, WorkListReusability&>
  instruction_handler_visitor_type;

  typedef InstructionVisitor<
  bool, GraphTask&, GraphAgenda&, WorkListReusability&>
  instruction_propagator_visitor_type;

  instruction_handler_visitor_type const&
  handleInstruction;

  instruction_propagator_visitor_type const&
  propagateOnUnmarked;

private:

  std::set<VirtualInstruction*> m_markedInstruction;
  Graph m_graph;

public:

  GraphAgenda(GraphAgenda const& origin) = delete;

  GraphAgenda(
      instruction_handler_visitor_type const& instruction_handler_visitor,
      instruction_propagator_visitor_type const& instruction_propagator_visitor,
      Function const& function)
    : handleInstruction(instruction_handler_visitor)
    , propagateOnUnmarked(instruction_propagator_visitor)
  { addNewAsFirst(new GraphTask(function.getFirstInstruction())); }

  virtual ~GraphAgenda(void);

  void markInstructionWith(
      VirtualInstruction &instruction,
      VirtualTask &task);

  bool saveAs(std::string const& filename);
};

} // compilation

// GraphHandler.
namespace compilation
{

template <InstructionType _Type>
struct GraphHandler
    : public InstructionHandler<_Type, GraphTask, GraphAgenda>
{
  void operator ()(
      VirtualInstruction* instr,
      GraphTask& task,
      GraphAgenda& continuations,
      WorkListReusability& reuse)
  {
    InstructionHandler<_Type, GraphTask, GraphAgenda>
        ::operator ()(instr,task,continuations,reuse);
  }
};

template <>
struct GraphHandler<TGoto>
    : public InstructionHandler<TGoto, GraphTask, GraphAgenda>
{
  void operator ()(
      VirtualInstruction* instr,
      GraphTask& task,
      GraphAgenda& continuations,
      WorkListReusability& reuse)
  {
    base_handler::operator ()(instr,task,continuations,reuse);
    assert(instr->getSNextInstruction());
    GraphAgenda::connectInstructionsInGraph(
          *instr, *instr->getSNextInstruction());
  }
};

template <>
struct GraphHandler<TIf>
    : public InstructionHandler<TIf, GraphTask, GraphAgenda>
{
  void operator ()(
      VirtualInstruction* instr,
      GraphTask& task,
      GraphAgenda& continuations,
      WorkListReusability& reuse)
  {
    base_handler::operator ()(instr,task,continuations,reuse);
    GraphAgenda::connectInstructionsInGraph(
          *instr, *instruction_cast<TIf>(instr)->getThen());
  }
};

template <>
struct GraphHandler<TLabel>
    : public InstructionHandler<TLabel, GraphTask, GraphAgenda>
{
  void operator ()(
      VirtualInstruction* instr,
      GraphTask& task,
      GraphAgenda& continuations,
      WorkListReusability& reuse)
  {
    base_handler::operator ()(instr,task,continuations,reuse);
    if (instruction_cast<TLabel>(instr)->getGoto())
      GraphAgenda::connectInstructionsInGraph(
            *instruction_cast<TLabel>(instr)->getGoto(), *instr);
  }
};

} // compilation

#endif // _COMPILATION_GRAPH_TASK_H_
